package com.renhe.utils;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookieStore;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;

import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jfinal.kit.StrKit;

/*用URLConnection实现的http请求工具类，先留着*/

/**
 * URLConnection是Java提供的原始方法
 */
public class URLConnectionHttpFetcher {
	
	private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    //返回内容
    public static class HttpResult {
        public int code;
        public byte[] body;
        public String msg;
        
        @Override
        public String toString() {
        	try{
        		return String.format("code:%d, msg:%s, body:%s", code, msg, new String(body, "UTF-8"));
        	}catch(Exception e){
        		return null;
        	}
        }
    }
    
    //默认超时时间10秒
    private static final String COOKIES_HEADER = "Set-Cookie";
    private String encoding = "UTF-8";
    //保存最后一次请求的响应头
    private Map<String, List<String>> respHeaders = new TreeMap<>();
    private int timeout = 10;
    private CookieManager cm = new CookieManager();
    private Map<String, String> defaultHeaders = new TreeMap<String, String>();
    
    public URLConnectionHttpFetcher(){
    	//默认都支持ssl吧
    	enableSSL();

        //默认头部
        defaultHeaders.put("Connection", "keep-alive");
        defaultHeaders.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        defaultHeaders.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36");
    }
    
    public void setTimeout(int secs){
    	this.timeout = secs;
    }

    //请求返回的cookie存起来
    private void saveCookie(URLConnection conn) {
        Map<String, List<String>> headerFields = conn.getHeaderFields();
        List<String> cookiesHeader = headerFields.get(COOKIES_HEADER);
        
        if (cookiesHeader != null) {
            for (String cookie : cookiesHeader) {
            	CookieStore cookieStore = cm.getCookieStore();
            	List<HttpCookie> list = HttpCookie.parse(cookie);
            	for(HttpCookie c : list){
	            	try{
	            		cookieStore.add(conn.getURL().toURI(), c);
	            	}catch(Exception e){logger.error("save cookie error", e);}
            	}
            }
        }
    }
    
    //如果需要，请求带上cookie
    private void withCookie(URLConnection conn) {
        List<HttpCookie> cookieList =  cm.getCookieStore().getCookies();
        if (cookieList.size() > 0) {
            // While joining the Cookies, use ',' or ';' as needed. Most of the servers are using ';'
            StringBuffer sb = new StringBuffer();

            for(HttpCookie cookie: cookieList) {
                sb.append(cookie.getName()).append("=").append(cookie.getValue()).append(";");
            }
           
            //拼上之前的cookie
            String origCookie = conn.getRequestProperty("Cookie");
            if(!StrKit.isBlank(origCookie)) {
                sb.append(origCookie);
            }
            conn.setRequestProperty("Cookie", sb.toString());
        }
    }
    
    public String getCookie(String name){
    	if(name == null)
    		return null;
    	
    	List<HttpCookie> cookieList = cm.getCookieStore().getCookies();
    	for(HttpCookie cookie : cookieList){
    		if(name.equals(cookie.getName())){
    			return cookie.getValue();
    		}
    	}
    	return null;
    }
    
    public List<HttpCookie> getCookies(){
    	return cm.getCookieStore().getCookies();
    }
    
    private byte[] readStream(InputStream is) {
        ByteArrayOutputStream bos = null;
        try {
            bos = new ByteArrayOutputStream();
            byte[] buff = new byte[1024];
            int count = -1;
            while ((count = is.read(buff, 0, 1024)) > 0) {
                bos.write(buff, 0, count);
            }
            bos.flush();
            return bos.toByteArray();
        } catch (Exception ignored) {
            //do nothing
        } finally {
            try {if(bos != null) bos.close();} catch(Exception ignored){}
        }
        
        return null;
    }
    
    
	private HostnameVerifier hv = new HostnameVerifier() {
		public boolean verify(String urlHostName, SSLSession session) {
			return true;
		}
	};

	private static void trustAllHttpsCertificates() throws Exception {
		javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
		javax.net.ssl.TrustManager tm = new miTM();
		trustAllCerts[0] = tm;
		javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance("SSL");
		sc.init(null, trustAllCerts, null);
		HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
	}
    
	static class miTM implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager {
		public java.security.cert.X509Certificate[] getAcceptedIssuers() {
			return null;
		}

		public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) {
			return true;
		}

		public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) {
			return true;
		}

		public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
				throws java.security.cert.CertificateException {
			return;
		}

		public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
				throws java.security.cert.CertificateException {
			return;
		}
	}
	
	public void enableSSL(){
		try{
			trustAllHttpsCertificates();
			HttpsURLConnection.setDefaultHostnameVerifier(hv);
		}catch(Exception e){
			logger.error("enableSSL exception", e);
		}
	}
    
    
    public HttpResult get(String requestUrl) {
       return get(requestUrl, false);
    }
    
    public HttpResult get(String requestUrl, boolean keepCookie) {
        return get(requestUrl, this.timeout, null, keepCookie);
    }
    
    public HttpResult get(String requestUrl, int timeout, boolean keepCookie) {
        return get(requestUrl, timeout, null, keepCookie);
    }
    
    public HttpResult get(String requestUrl, int timeout, Map<String, String> headerMap, boolean keepCookie) {
        return requestUrl.startsWith("https") ? _getBySsl(requestUrl, timeout, headerMap, keepCookie) 
                : _get(requestUrl, timeout, headerMap, keepCookie);
    }
    
    private Map<String, String> mergeHeader(Map<String, String> headerMap){
    	Map<String, String> headers = new TreeMap();
        //先把默认的头部带上
        for(Entry<String, String> e : defaultHeaders.entrySet()){
        	if(headers.get(e.getKey()) == null){
        		headers.put(e.getKey(), e.getValue()); 
        	}
        }
        //再带上自定义的头部
        if(headerMap != null){
        	headers.putAll(headerMap);
        }
        
        return headers;
    }
    
    private HttpResult _get(String requestUrl, int timeout, Map<String, String> headerMap, boolean keepCookie) {
        HttpResult result = new HttpResult();
        HttpURLConnection conn = null;
        URL url = null;
        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
        try {
            url = new URL(requestUrl);
            conn = (HttpURLConnection) url.openConnection();

            conn.setConnectTimeout(10000);
            conn.setReadTimeout(timeout * 1000);
            conn.setRequestMethod("GET");
            conn.setUseCaches(false);
            conn.setDoOutput(true);
            conn.setDoInput(true);

            //设置头部
            Map<String, String> headers = mergeHeader(headerMap);
            for (String headerName : headers.keySet()) {
                conn.setRequestProperty(headerName, headers.get(headerName));
            }

            // 如果keepcookie
            if(keepCookie) {
                withCookie(conn);
            }
            
            //保留最后一次请求的响应头
            respHeaders = conn.getHeaderFields();

            //拿返回
            result.code = conn.getResponseCode();
            result.msg = conn.getResponseMessage();
            result.body = readStream(conn.getInputStream());
            
            if(keepCookie) {
                saveCookie(conn);
            }
        } catch (Exception e) {
        	logger.error("error", e);
        	return null;
        } finally {
            if(conn != null)  conn.disconnect();
        }

        return result;
    }
    
    private HttpResult _getBySsl(String requestUrl, int timeout,  Map<String, String> headerMap, boolean keepCookie) {
        HttpResult result = new HttpResult();
        HttpsURLConnection conn = null;
        URL url = null;
        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
        try {
            url = new URL(requestUrl);
            conn = (HttpsURLConnection) url.openConnection();

            conn.setConnectTimeout(10000);
            conn.setReadTimeout(timeout * 1000);
            conn.setRequestMethod("GET");
            conn.setUseCaches(false);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            
            // 添加头部
            Map<String, String> headers = mergeHeader(headerMap);
            for (String headerName : headers.keySet()) {
                conn.setRequestProperty(headerName, headers.get(headerName));
            }

            // 如果keepcookie
            if(keepCookie) {
                withCookie(conn);
            }
            
            //响应头
            respHeaders = conn.getHeaderFields();

            //拿返回
            result.code = conn.getResponseCode();
            result.msg = conn.getResponseMessage();
            result.body = readStream(conn.getInputStream());

            if(keepCookie) {
                saveCookie(conn);
            }
        } catch (Exception e) {
        	logger.error("error", e);
        	return null;
        } finally {
            if(conn != null)  conn.disconnect();
        }

        return result;
    }
    
    public byte[] getb(String url, Map<String, String> headMap){
    	HttpResult result = get(url, this.timeout, headMap, true);
    	return result != null ? result.body : null;
    }

	public byte[] getb(String url){
		return getb(url, null);
	}
	
	public String gets(String url, Map<String, String> headMap){
		byte[] bs = getb(url, headMap);
		if(bs == null)
			return null;
		try{
			return new String(bs, this.encoding);
		}catch(Exception e){
			logger.error("error", e);
			return null;
		}
	}
	
	public String gets(String url){
		return gets(url, null);
	}
    
    public HttpResult post(String requestUrl) {
        return post(requestUrl, this.timeout, null, null, false);
    }
    
    public HttpResult post(String requestUrl, byte[] postData) {
        return post(requestUrl, this.timeout, null, postData, false);
    }
    
    public HttpResult post(String requestUrl, byte[] postData, boolean keepCookie) {
        return post(requestUrl, this.timeout, null, postData, keepCookie);
    }
    
    public HttpResult post(String requestUrl, int timeout,  byte[] postData, boolean keepCookie) {
        return post(requestUrl, timeout, null, postData, keepCookie);
    }
    
    public HttpResult post(String requestUrl, int timeout, Map<String, String> headerMap,  boolean keepCookie) {
        return post(requestUrl, timeout, headerMap, null, keepCookie);
    }
    
    public HttpResult post(String requestUrl, int timeout, Map<String, String> headerMap, byte[] postData, boolean keepCookie) {
        return requestUrl.startsWith("https") ? _postBySsl(requestUrl, timeout, headerMap, postData, keepCookie) 
                : _post(requestUrl, timeout, headerMap, postData, keepCookie);
    }
    
    private HttpResult _post(String requestUrl, int timeout, Map<String, String> headerMap, byte[] postData, boolean keepCookie) {
        HttpResult result = new HttpResult();
        HttpURLConnection conn = null;
        URL url = null;
        OutputStream os = null;
        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
        try {
            url = new URL(requestUrl);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(10000);
            conn.setReadTimeout(timeout * 1000);
            conn.setRequestMethod("POST");
            conn.setUseCaches(false);
            conn.setDoOutput(true);
            conn.setDoInput(true);

            // 添加头部
            Map<String, String> headers = mergeHeader(headerMap);
            for (String headerName : headers.keySet()) {
                conn.setRequestProperty(headerName, headers.get(headerName));
            }

            // 如果keepcookie
            if(keepCookie) {
                withCookie(conn);
            }
            
            //写post内容
            if(postData != null && postData.length > 0) {
                os = conn.getOutputStream();
                os.write(postData);
                os.flush();
            }
            
            //响应头
            respHeaders = conn.getHeaderFields();

            //拿返回
            result.code = conn.getResponseCode();
            result.msg = conn.getResponseMessage();
            result.body = readStream(conn.getInputStream());

            if(keepCookie) {
                saveCookie(conn);
            }
        } catch (Exception e) {
        	logger.error("error", e);
        	return null;
        } finally {
            if(conn != null)  conn.disconnect();
            try {if(os != null)  os.close();} catch(Exception ignored) {}
        }

        return result;        
    }
    
    
    private HttpResult _postBySsl(String requestUrl, int timeout, Map<String, String> headerMap, byte[] postData, boolean keepCookie) {
        HttpResult result = new HttpResult();
        HttpsURLConnection conn = null;
        URL url = null;
        OutputStream os = null;
        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
        try {
            url = new URL(requestUrl);
            conn = (HttpsURLConnection) url.openConnection();

            conn.setConnectTimeout(10000);
            conn.setReadTimeout(timeout * 1000);
            conn.setRequestMethod("POST");
            conn.setUseCaches(false);
            conn.setDoOutput(true);
            conn.setDoInput(true);

            // 添加头部
            // FIXME 这里我严重怀疑有一些头部是设置失败的，但是我没有https服务来验证！比如 Content-Type 很可能就没有设置成功，夜神渠道的登录就不行！
            Map<String, String> headers = mergeHeader(headerMap);
            for (String headerName : headers.keySet()) {
                conn.setRequestProperty(headerName, headers.get(headerName));
            }

            // 如果keepcookie
            if(keepCookie) {
                withCookie(conn);
            }

            //写post内容
            if(postData != null && postData.length > 0) {
                os = conn.getOutputStream();
                os.write(postData);
                os.flush();
            }
 
            //响应头
            respHeaders = conn.getHeaderFields();

            //拿返回
            result.code = conn.getResponseCode();
            result.msg = conn.getResponseMessage();
            result.body = readStream(conn.getInputStream());
            
            //System.out.println("result:" + result);
            if(keepCookie) {
                saveCookie(conn);
            }
        } catch (Exception e) {
        	logger.error("error", e);
        	return null;
        } finally {
            if(conn != null)  conn.disconnect();
            try {if(os != null)  os.close();} catch(Exception ignored) {}
        }

        return result;   
    }
    
    private byte[] postb(String url, HttpEntity data, Map<String, String> headMap){
    	try{
	    	byte[] postData = readStream(data.getContent());
	    	HttpResult result = post(url, this.timeout, headMap, postData, true);
	    	return result != null ? result.body : null;
    	}catch(Exception e){
    		logger.error("error", e);
    		return null;
    	}
    }
  
    public byte[] postb(String url, byte[] data, Map<String, String> headMap){
		return postb(url, new ByteArrayEntity(data), headMap);
	}
	
	public byte[] postb(String url, byte[] data){
		return postb(url, new ByteArrayEntity(data), null);
	}
	
	public byte[] postb(String url, String data, Map<String, String> headMap){
		try{
			return postb(url, data.getBytes("UTF-8"), headMap);
		}catch(Exception e){
			logger.error("error", e);
			return null;
		}
	}
	
	public byte[] postb(String url, String data){
		return postb(url, data, null);
	}
	
	public byte[] postb(String url, List<NameValuePair> data, Map<String, String> headMap){
		try{
			return postb(url, new UrlEncodedFormEntity(data, this.encoding), headMap);
		}catch(Exception e){
			logger.error("error", e);
			return null;
		}
	}
	
	public String posts(String url, List<NameValuePair> data, Map<String, String> headMap){
		try{
			byte[] content = postb(url, new UrlEncodedFormEntity(data, this.encoding), headMap);
			if(content == null)
				return null;
			return new String(content, this.encoding);
		}catch(Exception e){
			logger.error("error", e);
			return null;
		}
	}
	
	public String posts(String url, List<NameValuePair> data){
		return posts(url, data, null);
	}
	
	public String posts(String url, String data, Map<String, String> headMap){
		try{
			byte[] bs = postb(url, data.getBytes(this.encoding), headMap);
			if(bs == null)
				return null;
			return new String(bs, this.encoding);
		}catch(Exception e){
			logger.error("error", e);
			return null;
		}
	}
	
	public String posts(String url, String data){
		return posts(url, data, null);
	}
	
	public Header[] getResponseHeaders(String name){
		if(respHeaders == null)
			return new Header[0];
		
		List<String> values = respHeaders.get(name);
		if(values == null)
			return new Header[0];
		
		Header[] headers = new Header[values.size()];
		for(int i=0; i<headers.length; ++i){
			headers[i] = new BasicHeader(name, values.get(i));
		}
		return headers;
	}
	
	public Header[] getResponseHeaders(){
		if(respHeaders == null)
			return new Header[0];
		
		List<Header> headers = new ArrayList();
		for(Entry<String, List<String>> e : respHeaders.entrySet()){
			if(StringUtils.isBlank(e.getKey())){
				continue;
			}
			for(String v : e.getValue()){
				headers.add(new BasicHeader(e.getKey(), v));
			}
		}
		return headers.toArray(new Header[0]);
	}
	
	
//	public Header[] getResponseHeaders(){
//		if(respHeaders == null)
//			return new Header[0];
//	}
	
//    public static void main(String[] args) throws Exception{
//        HttpFetcher http = new HttpFetcher();
//        
//        HttpResult result = http.get("https://pay.360.cn/", true);
//        System.out.println(result);
//    }
    
}
